#ifndef _PUBLIC_H
#define _PUBLIC_H 1
#include "_cmpublic.h"

using namespace std;

namespace idc
{
    ///////////////////////////////////// /////////////////////////////////////
    // C++风格字符串操作的若干函数。
    // 删除字符串左边指定的字符。
    // str：待处理的字符串。
    // cc：需要删除的字符，缺省删除空格。
    char *deletelchr(char *str, const int cc = ' ');
    string &deletelchr(string &str, const int cc = ' ');

    // 删除字符串右边指定的字符。
    // str：待处理的字符串。
    // cc：需要删除的字符，缺省删除空格。
    char *deleterchr(char *str, const int cc = ' ');
    string &deleterchr(string &str, const int cc = ' ');

    // 删除字符串左右两边指定的字符。
    // str：待处理的字符串。
    // chr：需要删除的字符，缺省删除空格。
    char *deletelrchr(char *str, const int cc = ' ');
    string &deletelrchr(string &str, const int cc = ' ');

    // 把字符串中的小写字母转换成大写，忽略不是字母的字符。
    // str：待转换的字符串。
    char *toupper(char *str);
    string &toupper(string &str);

    // 把字符串中的大写字母转换成小写，忽略不是字母的字符。
    // str：待转换的字符串。
    char *tolower(char *str);
    string &tolower(string &str);

    // 字符串替换函数。
    // 在字符串str中，如果存在字符串str1，就替换为字符串str2。
    // str：待处理的字符串。
    // str1：旧的内容。
    // str2：新的内容。
    // bloop：是否循环执行替换。
    // 注意：
    // 1、如果str2比str1要长，替换后str会变长，所以必须保证str有足够的空间，否则内存会溢出（C++风格字符串不存在这个问题）。
    // 2、如果str2中包含了str1的内容，且bloop为true，这种做法存在逻辑错误，replacestr将什么也不做。
    // 3、如果str2为空，表示删除str中str1的内容。
    bool replacestr(char *str, const string &str1, const string &str2, const bool bloop = false);
    bool replacestr(string &str, const string &str1, const string &str2, const bool bloop = false);

    // 从一个字符串中提取出数字、符号和小数点，存放到另一个字符串中。
    // src：原字符串。
    // dest：目标字符串。
    // bsigned：是否提取符号（+和-），true-包括；false-不包括。
    // bdot：是否提取小数点（.），true-包括；false-不包括。
    // 注意：src和dest可以是同一个变量。
    char *picknumber(const string &src, char *dest, const bool bsigned = false, const bool bdot = false);
    string &picknumber(const string &src, string &dest, const bool bsigned = false, const bool bdot = false);
    string picknumber(const string &src, const bool bsigned = false, const bool bdot = false);

    // 正则表达式，判断一个字符串是否匹配另一个字符串。
    // str：需要判断的字符串，是精确表示的，如文件名"_public.cpp"。
    // rules：匹配规则的表达式，用星号"*"代表任意字符，多个表达式之间用半角的逗号分隔，如"*.h,*.cpp"。
    // 注意：1）str参数不需要支持"*"，rules参数支持"*"；2）函数在判断str是否匹配rules的时候，会忽略字母的大小写。
    bool matchstr(const string &str, const string &rules);
    ///////////////////////////////////// /////////////////////////////////////

    ///////////////////////////////////// /////////////////////////////////////
    // ccmdstr类用于拆分有分隔符的字符串。
    // 字符串的格式为：字段内容1+分隔符+字段内容2+分隔符+字段内容3+分隔符+...+字段内容n。
    // 例如："messi,10,striker,30,1.72,68.5,Barcelona"，这是足球运动员梅西的资料。
    // 包括：姓名、球衣号码、场上位置、年龄、身高、体重和效力的俱乐部，字段之间用半角的逗号分隔。
    class ccmdstr
    {
    private:
        vector<string> m_cmdstr; // 存放拆分后的字段内容。

        ccmdstr(const ccmdstr &) = delete;            // 禁用拷贝构造函数。
        ccmdstr &operator=(const ccmdstr &) = delete; // 禁用赋值函数。
    public:
        ccmdstr() {} // 构造函数。
        ccmdstr(const string &buffer, const string &sepstr, const bool bdelspace = false);

        const string &operator[](int ii) const // 重载[]运算符，可以像访问数组一样访问m_cmdstr成员。
        {
            return m_cmdstr[ii];
        }

        // 把字符串拆分到m_cmdstr容器中。
        // buffer：待拆分的字符串。
        // sepstr：buffer中采用的分隔符，注意，sepstr参数的数据类型不是字符，是字符串，如","、" "、"|"、"~!~"。
        // bdelspace：拆分后是否删除字段内容前后的空格，true-删除；false-不删除，缺省不删除。
        void splittocmd(const string &buffer, const string &sepstr, const bool bdelspace = false);

        // 获取拆分后字段的个数，即m_cmdstr容器的大小。
        int size() const { return m_cmdstr.size(); }
        int cmdcount() const { return m_cmdstr.size(); } // 兼容以前的项目。

        // 从m_cmdstr容器获取字段内容。
        // ii：字段的顺序号，类似数组的下标，从0开始。
        // value：传入变量的地址，用于存放字段内容。
        // 返回值：true-成功；如果ii的取值超出了m_cmdstr容器的大小，返回失败。
        bool getvalue(const int ii, string &value, const int ilen = 0) const; // C++风格字符串。视频中没有第三个参数，加上第三个参数更好。
        bool getvalue(const int ii, char *value, const int ilen = 0) const;   // C风格字符串，ilen缺省值为0-全部长度。
        bool getvalue(const int ii, int &value) const;                        // int整数。
        bool getvalue(const int ii, unsigned int &value) const;               // unsigned int整数。
        bool getvalue(const int ii, long &value) const;                       // long整数。
        bool getvalue(const int ii, unsigned long &value) const;              // unsigned long整数。
        bool getvalue(const int ii, double &value) const;                     // 双精度double。
        bool getvalue(const int ii, float &value) const;                      // 单精度float。
        bool getvalue(const int ii, bool &value) const;                       // bool型。

        ~ccmdstr() {} // 析构函数。
    };

    // 重载<<运算符，输出ccmdstr::m_cmdstr中的内容，方便调试。
    ostream &operator<<(ostream &out, const ccmdstr &cc);
    ///////////////////////////////////// /////////////////////////////////////

    ///////////////////////////////////// /////////////////////////////////////
    // 解析xml格式字符串的函数族。
    // xml格式的字符串的内容如下：
    // <filename>/tmp/_public.h</filename><mtime>2020-01-01 12:20:35</mtime><size>18348</size>
    // <filename>/tmp/_public.cpp</filename><mtime>2020-01-01 10:10:15</mtime><size>50945</size>
    // xmlbuffer：待解析的xml格式字符串。
    // fieldname：字段的标签名。
    // value：传入变量的地址，用于存放字段内容，支持bool、int、insigned int、long、
    //       unsigned long、double和char[]。
    // 注意：当value参数的数据类型为char []时，必须保证value数组的内存足够，否则可能发生内存溢出的问题，
    //           也可以用ilen参数限定获取字段内容的长度，ilen的缺省值为0，表示不限长度。
    // 返回值：true-成功；如果fieldname参数指定的标签名不存在，返回失败。
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, string &value, const int ilen = 0); // 视频中没有第三个参数，加上第三个参数更好。
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, char *value, const int ilen = 0);
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, bool &value);
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, int &value);
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, unsigned int &value);
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, long &value);
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, unsigned long &value);
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, double &value);
    bool getxmlbuffer(const string &xmlbuffer, const string &fieldname, float &value);
    ///////////////////////////////////// /////////////////////////////////////

    template <typename... Args>
    bool sformat(string &str, const char *fmt, Args... args)
    {
        int len = snprintf(nullptr, 0, fmt, args...); // 得到格式化输出后字符串的总长度。
        if (len < 0)
            return false; // 如果调用snprintf失败，返回-1。
        if (len == 0)
        {
            str.clear();
            return true;
        } // 如果调用snprintf返回0，表示格式化输出的内容为空。

        str.resize(len);                          // 为string分配内存。
        snprintf(&str[0], len + 1, fmt, args...); // linux平台第二个参数是len+1，windows平台是len。
        return true;
    }
    template <typename... Args>
    string sformat(const char *fmt, Args... args)
    {
        string str;

        int len = snprintf(nullptr, 0, fmt, args...); // 得到格式化后字符串的长度。
        if (len < 0)
            return str; // 如果调用snprintf失败，返回-1。
        if (len == 0)
            return str; // 如果调用snprintf返回0，表示格式化输出的内容为空。;

        str.resize(len);                          // 为string分配内存。
        snprintf(&str[0], len + 1, fmt, args...); // linux平台第二个参数是len+1，windows平台是len。
        return str;
    }
    ///////////////////////////////////// /////////////////////////////////////
    // 时间操作的若干函数。
    /*
      取操作系统的时间（用字符串表示）。
      strtime：用于存放获取到的时间。
      timetvl：时间的偏移量，单位：秒，0是缺省值，表示当前时间，30表示当前时间30秒之后的时间点，-30表示当前时间30秒之前的时间点。
      fmt：输出时间的格式，fmt每部分的含义：yyyy-年份；mm-月份；dd-日期；hh24-小时；mi-分钟；ss-秒，
      缺省是"yyyy-mm-dd hh24:mi:ss"，目前支持以下格式：
      "yyyy-mm-dd hh24:mi:ss"
      "yyyymmddhh24miss"
      "yyyy-mm-dd"
      "yyyymmdd"
      "hh24:mi:ss"
      "hh24miss"
      "hh24:mi"
      "hh24mi"
      "hh24"
      "mi"
      注意：
        1）小时的表示方法是hh24，不是hh，这么做的目的是为了保持与数据库的时间表示方法一致；
        2）以上列出了常用的时间格式，如果不能满足你应用开发的需求，请修改源代码timetostr()函数增加更多的格式支持；
        3）调用函数的时候，如果fmt与上述格式都匹配，strtime的内容将为空。
        4）时间的年份是四位，其它的可能是一位和两位，如果不足两位，在前面补0。
    */
    string &ltime(string &strtime, const string &fmt = "", const int timetvl = 0);
    char *ltime(char *strtime, const string &fmt = "", const int timetvl = 0);
    // 为了避免重载的岐义，增加ltime1()函数。
    string ltime1(const string &fmt = "", const int timetvl = 0);

    // 把整数表示的时间转换为字符串表示的时间。
    // ttime：整数表示的时间。
    // strtime：字符串表示的时间。
    // fmt：输出字符串时间strtime的格式，与ltime()函数的fmt参数相同，如果fmt的格式不正确，strtime将为空。
    string &timetostr(const time_t ttime, string &strtime, const string &fmt = "");
    char *timetostr(const time_t ttime, char *strtime, const string &fmt = "");
    // 为了避免重载的岐义，增加timetostr1()函数。
    string timetostr1(const time_t ttime, const string &fmt = "");

    // 把字符串表示的时间转换为整数表示的时间。
    // strtime：字符串表示的时间，格式不限，但一定要包括yyyymmddhh24miss，一个都不能少，顺序也不能变。
    // 返回值：整数表示的时间，如果strtime的格式不正确，返回-1。
    time_t strtotime(const string &strtime);

    // 把字符串表示的时间加上一个偏移的秒数后得到一个新的字符串表示的时间。
    // in_stime：输入的字符串格式的时间，格式不限，但一定要包括yyyymmddhh24miss，一个都不能少，顺序也不能变。
    // out_stime：输出的字符串格式的时间。
    // timetvl：需要偏移的秒数，正数往后偏移，负数往前偏移。
    // fmt：输出字符串时间out_stime的格式，与ltime()函数的fmt参数相同。
    // 注意：in_stime和out_stime参数可以是同一个变量的地址，如果调用失败，out_stime的内容会清空。
    // 返回值：true-成功，false-失败，如果返回失败，可以认为是in_stime的格式不正确。
    bool addtime(const string &in_stime, char *out_stime, const int timetvl, const string &fmt = "");
    bool addtime(const string &in_stime, string &out_stime, const int timetvl, const string &fmt = "");
    ///////////////////////////////////// /////////////////////////////////////

    ///////////////////////////////////// /////////////////////////////////////
    // 这是一个精确到微秒的计时器。
    class ctimer
    {
    private:
        struct timeval m_start; // 计时开始的时间点。
        struct timeval m_end;   // 计时结束的时间点。
    public:
        ctimer(); // 构造函数中会调用start方法。

        void start(); // 开始计时。

        // 计算已逝去的时间，单位：秒，小数点后面是微秒。
        // 每调用一次本方法之后，自动调用start方法重新开始计时。
        double elapsed();

        ~ctimer() // 析构函数。
        {
        }
    };
    ///////////////////////////////////////////////////////////////////////////////////////////////////
    // 根据绝对路径的文件名或目录名逐级的创建目录。
    // pathorfilename：绝对路径的文件名或目录名。
    // bisfilename：指定pathorfilename的类型，true-pathorfilename是文件名，否则是目录名，缺省值为true。
    // 返回值：true-成功，false-失败，如果返回失败，原因有大概有三种情况：
    // 1）权限不足；2）pathorfilename参数不是合法的文件名或目录名；3）磁盘空间不足。
    bool newdir(const string &pathorfilename, const bool bisfilename = true);

    ///////////////////////////////////// /////////////////////////////////////
    // 文件操作相关的函数

    // 重命名文件，类似Linux系统的mv命令。
    // srcfilename：原文件名，建议采用绝对路径的文件名。
    // dstfilename：目标文件名，建议采用绝对路径的文件名。
    // 返回值：true-成功；false-失败，失败的主要原因是权限不足或磁盘空间不够，如果原文件和目标文件不在同一个磁盘分区，重命名也可能失败。
    // 注意，在重命名文件之前，会自动创建dstfilename参数中包含的目录。
    // 在应用开发中，可以用renamefile()函数代替rename()库函数。
    bool renamefile(const string &srcfilename, const string &dstfilename);

    // 复制文件，类似Linux系统的cp命令。
    // srcfilename：原文件名，建议采用绝对路径的文件名。
    // dstfilename：目标文件名，建议采用绝对路径的文件名。
    // 返回值：true-成功；false-失败，失败的主要原因是权限不足或磁盘空间不够。
    // 注意：
    // 1）在复制文件之前，会自动创建dstfilename参数中的目录名。
    // 2）复制文件的过程中，采用临时文件命名的方法，复制完成后再改名为dstfilename，避免中间状态的文件被读取。
    // 3）复制后的文件的时间与原文件相同，这一点与Linux系统cp命令不同。
    bool copyfile(const string &srcfilename, const string &dstfilename);

    // 获取文件的大小。
    // filename：待获取的文件名，建议采用绝对路径的文件名。
    // 返回值：如果文件不存在或没有访问权限，返回-1，成功返回文件的大小，单位是字节。
    int filesize(const string &filename);

    // 获取文件的时间。
    // filename：待获取的文件名，建议采用绝对路径的文件名。
    // mtime：用于存放文件的时间，即stat结构体的st_mtime
    // 返回值：true-成功；false-失败，失败的原因保存在errno中。
    bool filemtime(const string &filename, char *mtime, const string &fmt = "yyyymmddhh24miss");
    bool filemtime(const string &filename, string &mtime, const string &fmt = "yyyymmddhh24miss");

    // 重置文件的修改时间属性。
    // filename：待重置的文件名，建议采用绝对路径的文件名。
    // mtime：字符串表示的时间，格式不限，但一定要包括yyyymmddhh24miss，一个都不能少，顺序也不能变。
    // 返回值：true-成功；false-失败，失败的原因保存在errno中。
    bool setmtime(const string &filename, const string &mtime);
    ///////////////////////////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////// /////////////////////////////////////
    // 获取某目录及其子目录中的文件列表的类。
    class cdir
    {
    private:
        vector<string> m_filelist; // 存放文件列表的容器（绝对路径的文件名）。
        int m_pos;                 // 从文件列表m_filelist中已读取文件的位置。
        string m_fmt;              // 文件时间格式，缺省"yyyymmddhh24miss"。

        cdir(const cdir &) = delete;            // 禁用拷贝构造函数。
        cdir &operator=(const cdir &) = delete; // 禁用赋值函数。
    public:
        // /project/public/_public.h
        string m_dirname;   // 目录名，例如：/project/public
        string m_filename;  // 文件名，不包括目录名，例如：_public.h
        string m_ffilename; // 绝对路径的文件，例如：/project/public/_public.h
        int m_filesize;     // 文件的大小，单位：字节。
        string m_mtime;     // 文件最后一次被修改的时间，即stat结构体的st_mtime成员。
        string m_ctime;     // 文件生成的时间，即stat结构体的st_ctime成员。
        string m_atime;     // 文件最后一次被访问的时间，即stat结构体的st_atime成员。

        cdir() : m_pos(0), m_fmt("yyyymmddhh24miss") {} // 构造函数。

        // 设置文件时间的格式，支持"yyyy-mm-dd hh24:mi:ss"和"yyyymmddhh24miss"两种，缺省是后者。
        void setfmt(const string &fmt);

        // 打开目录，获取目录中文件的列表，存放在m_filelist容器中。
        // dirname，目录名，采用绝对路径，如/tmp/root。
        // rules，文件名的匹配规则，不匹配的文件将被忽略。
        // maxfiles，本次获取文件的最大数量，缺省值为10000个，如果文件太多，可能消耗太多的内存。
        // bandchild，是否打开各级子目录，缺省值为false-不打开子目录。
        // bsort，是否按文件名排序，缺省值为false-不排序。
        // 返回值：true-成功，false-失败。
        bool opendir(const string &dirname, const string &rules, const int maxfiles = 10000, const bool bandchild = false, bool bsort = false);

    private:
        // 这是一个递归函数，被opendir()的调用，在cdir类的外部不需要调用它。
        bool _opendir(const string &dirname, const string &rules, const int maxfiles, const bool bandchild);

    public:
        // 从m_filelist容器中获取一条记录（文件名），同时获取该文件的大小、修改时间等信息。
        // 调用opendir方法时，m_filelist容器被清空，m_pos归零，每调用一次readdir方法m_pos加1。
        // 当m_pos小于m_filelist.size()，返回true，否则返回false。
        bool readdir();

        unsigned int size() { return m_filelist.size(); }

        ~cdir(); // 析构函数。
    };
    ///////////////////////////////////// /////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    // 写文件的类。
    class cofile // class out file
    {
    private:
        ofstream fout;       // 写入文件的对象。
        string ffilename;    // 文件名，建议采用绝对路径。
        string ffilenametmp; // 临时文件名，在m_filename后面加".tmp"。
    public:
        cofile() {}
        bool isopen() const { return fout.is_open(); } // 文件是否已打开。

        // 打开文件。
        // filename，待打开的文件名。
        // btmp，是否采用临时文件的方案。
        // mode，打开文件的模式。
        // benbuffer，是否启用文件缓冲区。
        bool open(const string &filename, const bool btmp = true, const ios::openmode mode = ios::out, const bool benbuffer = true);

        // 把数据以文本的方式格式化输出到文件。
        template <typename... Args>
        bool writeline(const char *fmt, Args... args)
        {
            if (fout.is_open() == false)
                return false;

            fout << sformat(fmt, args...);

            return fout.good();
        }

        // 重载<<运算符，把数据以文本的方式输出到文件。
        // 注意：换行只能用\n，不能用endl。
        template <typename T>
        cofile &operator<<(const T &value)
        {
            fout << value;
            return *this;
        }

        // 把二进制数据写入文件。
        bool write(void *buf, int bufsize);

        // 关闭文件，并且把临时文件名改为正式文件名。
        bool closeandrename();

        // 关闭文件，如果有临时文件，则删除它。
        void close();

        ~cofile() { close(); };
    };
    ///////////////////////////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    // 读取文件的类。
    class cifile // class in file
    {
    private:
        ifstream fin;     // 读取文件的对象。
        string ffilename; // 文件名，建议采用绝对路径。
    public:
        cifile() {}

        // 判断文件是否已打开。
        bool isopen() const { return fin.is_open(); }

        // 打开文件。
        // filename，待打开的文件名。
        // mode，打开文件的模式。
        bool open(const string &filename, const ios::openmode mode = ios::in);

        // 以行的方式读取文本文件，endbz指定行的结尾标志，缺省为空，没有结尾标志。
        bool readline(string &buf, const string &endbz = "");

        // 读取二进制文件，返回实际读取到的字节数。
        int read(void *buf, const int bufsize);

        // 关闭并删除文件。
        bool closeandremove();

        // 只关闭文件。
        void close();

        ~cifile() { close(); }
    };
    ///////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////
    // 自旋锁。
    class spinlock_mutex
    {
    private:
        atomic_flag flag;

        spinlock_mutex(const spinlock_mutex &) = delete;
        spinlock_mutex &operator=(const spinlock_mutex) = delete;

    public:
        spinlock_mutex()
        {
            flag.clear();
        }
        void lock() // 加锁。
        {
            while (flag.test_and_set())
                ;
        }
        void unlock() // 解锁。
        {
            flag.clear();
        }
    };

    ///////////////////////////////////// /////////////////////////////////////
    // 日志文件。
    class clogfile
    {
        ofstream fout;         // 日志文件对象。
        string m_filename;     // 日志文件名，建议采用绝对路径。
        ios::openmode m_mode;  // 日志文件的打开模式。
        bool m_backup;         // 是否自动切换日志。
        int m_maxsize;         // 当日志文件的大小超过本参数时，自动切换日志。
        bool m_enbuff;         // 是否启用文件缓冲区。
        spinlock_mutex m_lock; // 自旋锁，用于多线程程序中给写日志的操作加锁。

    public:
        // 构造函数，日志文件的大小缺省100M。
        clogfile(int maxsize = 100) : m_maxsize(maxsize) {}

        // 打开日志文件。
        // filename：日志文件名，建议采用绝对路径，如果文件名中的目录不存在，就先创建目录。
        // openmode：日志文件的打开模式，缺省值是ios::app。
        // bbackup：是否自动切换（备份），true-切换，false-不切换，在多进程的服务程序中，如果多个进程共用一个日志文件，bbackup必须为false。
        // benbuffer：是否启用文件缓冲机制，true-启用，false-不启用，如果启用缓冲区，那么写进日志文件中的内容不会立即写入文件，缺省是不启用。
        // 注意，在多进程的程序中，多个进程往同一日志文件写入大量的日志时，可能会出现小混乱，但是，多线程不会。
        // 1）多个进程往同一日志文件写入大量的日志时，可能会出现小混乱，这个问题并不严重，可以容忍；
        // 2）只有同时写大量日志时才会出现混乱，在实际开发中，这种情况不多见。
        // 3）如果业务无法容忍，可以用信号量加锁。
        bool open(const string &filename, const ios::openmode mode = ios::app, const bool bbackup = true, const bool benbuffer = false);

        // 把日志内容以文本的方式格式化输出到日志文件，并且，在日志内容前面写入时间。
        template <typename... Args>
        bool write(const char *fmt, Args... args)
        {
            if (fout.is_open() == false)
                return false;

            backup(); // 判断是否需要切换日志文件。

            m_lock.lock();                                    // 加锁。
            fout << ltime1() << " " << sformat(fmt, args...); // 把当前时间和日志内容写入日志文件。
            m_lock.unlock();                                  // 解锁。

            return fout.good();
        }

        // 重载<<运算符，把日志内容以文本的方式输出到日志文件，不会在日志内容前面写时间。
        // 注意：内容换行用\n，不能用endl。
        template <typename T>
        clogfile &operator<<(const T &value)
        {
            m_lock.lock();
            fout << value;
            m_lock.unlock();

            return *this;
        }

    private:
        // 如果日志文件的大小超过m_maxsize的值，就把当前的日志文件名改为历史日志文件名，再创建新的当前日志文件。
        // 备份后的文件会在日志文件名后加上日期时间，如/tmp/log/filetodb.log.20200101123025。
        // 注意，在多进程的程序中，日志文件不可切换，多线的程序中，日志文件可以切换。
        bool backup();

    public:
        void close() { fout.close(); }

        ~clogfile() { close(); };
    };
    ///////////////////////////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    // socket通讯的客户端类
    class ctcpclient
    {
    private:
        int m_socket;             // 客户端的socket
        unsigned int server_port; // 服务端的端口
        string server_ip;         // 服务端的ip
    public:
        ctcpclient() { m_socket = -1; }
        bool Connect(const unsigned int port, const string &ip);          // 客户端连接服务端
        bool Read(string &buff, const int itimeout = 0);                  // 接收文本数据
        bool Read(void *buff, const int bufflen, const int itimeout = 0); // 接收二进制数据
        bool Write(const string &buff);                                   // 发送文本数据
        bool Write(const void *buff, const int bufflen);                  // 发送二进制数据
        void Close();                                                     // 关闭连接
        ~ctcpclient() { Close(); }
    };

    // socket通讯的服务端类
    class ctcpserver
    {
    public:
        int m_listensock;               // 服务端的监听socket
        int m_connsock;                 // 已连接的客户端socket
        int sockaddr_len;               // 客户端地址的长度
        struct sockaddr_in server_addr; // 服务端地址
        struct sockaddr_in client_addr; // 客户端地址
    public:
        ctcpserver()
        {
            m_listensock = -1;
            m_connsock = -1;
        }
        bool Initserver(const unsigned int port, const int backlog = 5);  // 初始化服务端
        bool Accept();                                                    // 从已连接队列中获取一个客户端连接
        bool Read(string &buff, const int itimeout = 0);                  // 接收文本数据
        bool Read(void *buff, const int bufflen, const int itimeout = 0); // 接收二进制数据
        bool Write(const string &buff);                                   // 发送文本数据
        bool Write(const void *buff, const int bufflen);                  // 发送二进制数据
        char *getclientip();                                              // 获取客户端的ip
        void Closelisten();                                               // 关闭监听socket
        void Closeconn();                                                 // 关闭已连接的客户端socket
        ~ctcpserver()
        {
            Closeconn();
            Closelisten();
        }
    };

    // 接收对端发送过来的数据。
    // buffer：存放接收数据的缓冲区。
    // ibuflen: 打算接收数据的大小。
    // itimeout：等待数据的超时时间（秒）：-1-不等待；0-无限等待；>0-等待的秒数。
    // 返回值：true-成功；false-失败，失败有两种情况：1）等待超时；2）socket连接已不可用。
    bool tcpread(const int sockfd, void *buffer, const int ibuflen, const int itimeout = 0); // 接收二进制数据
    bool tcpread(const int sockfd, string &buffer, const int itimeout = 0);                  // 接收文本数据

    // 向对端发送数据。
    // buffer：待发送数据缓冲区。
    // ibuflen：待发送数据的大小。
    // 返回值：true-成功；false-失败，如果失败，表示socket连接已不可用。
    bool tcpwrite(const int sockfd, const void *buffer, const int ibuflen); // 发送二进制数据
    bool tcpwrite(const int sockfd, const string &buffer);                  // 发送文本数据

    // 从已经准备好的socket中读取数据。
    // sockfd：已经准备好的socket连接。
    // buffer：存放数据的地址。
    // n：本次打算读取数据的字节数。
    // 返回值：成功接收到n字节的数据后返回true，socket连接不可用返回false.
    bool readn(const int sockfd, char *buffer, const size_t n);

    // 向已经准备好的socket中写入数据。
    // sockfd：已经准备好的socket连接。
    // buffer：待写入数据的地址。
    // n：待写入数据的字节数。
    // 返回值：成功写入完n字节的数据后返回true，socket连接不可用返回false。
    bool writen(const int sockfd, const char *buffer, const size_t n);

    void closeallsign();

    class csemp
    {
    private:
        union semun // 用于操控共享内存的联合体
        {
            int value;
            struct semid_ds *buf;
            unsigned short *arry;
        };
        int m_semid; // 信号量id

        /*如果将m_semflg设为SEM_UNOD,操作系统将跟踪进程对信号量的修改,在全部修改过信号量的进程终止后将信号量设置为初始值
        m_semflag=1时用于互斥锁，m_semflag=0时用于生产消费者模型*/
        short m_semflg;                           // 信号量值
        csemp(const csemp &) = delete;            // 禁用拷贝构造函数
        csemp &operator=(const csemp &) = delete; // 禁用赋值运算符

    public:
        csemp() : m_semid(-1) {}
        /*如果信号量已存在，就获取信号量
        如果信号量不存在，就创建信号量并将其初始化为value
        互斥锁时，value=1,semflag=SEM_UNOD
        生产消费者模型时，value=0，semflag=0*/
        bool init(key_t key, unsigned short value = 1, short semflg = SEM_UNDO);
        bool wait(short value = -1); // P操作
        bool post(short value = 1);  // V操作
        int getvalue();
        bool destroy();
        ~csemp();
    };

    // 循环队列
    template <typename T, int MaxLength>
    class squeue
    {
    private:
        int m_inited = -1;
        int m_head;
        int m_tail;
        int m_length;
        T m_data[MaxLength];
        squeue(const squeue &) = delete;
        squeue &operator=(const squeue &) = delete;

    public:
        squeue() { init(); }
        bool init()
        {
            if (m_inited != -1)
                return false;
            m_inited = 1;
            m_head = 0;
            m_tail = 0;
            m_length = 0;
            memset(m_data, 0, sizeof(m_data));
            return true;
        }
        bool push(const T &data)
        {
            if (full())
                return false;
            m_data[m_tail] = data;
            m_tail = (m_tail + 1) % MaxLength;
            m_length++;
            return true;
        }

        bool pop()
        {
            if (empty())
                return false;
            m_head = (m_head + 1) % MaxLength;
            m_length--;
            return true;
        }

        bool empty()
        {
            return m_length == 0;
        }

        bool full()
        {
            return m_length == MaxLength;
        }

        int size()
        {
            return m_length;
        }
        T &front()
        {
            return m_data[m_head];
        }

        void Print()
        {
            for (int i = m_head; i < m_tail; i++)
            {
                cout << m_data[i] << " ";
            }
        }

        ~squeue() {}
    };

    // 进程心跳有关的类

    struct st_procinfo // 存储进程心跳信息的结构体
    {
        int pid;                // 进程编号
        char name[50] = {0};    // 进程名称
        int timeout;            // 超时时间
        time_t atime;           // 最后一次心跳时间
        st_procinfo() = default; // 默认构造函数
        st_procinfo(int pid, char *name, int timeout, time_t atime) : pid(pid), timeout(timeout), atime(atime)
        {
            strcpy(this->name, name);
        }
    };

    #define MAXNUM 1024  // 最大进程数
    #define SHMKEYP 0x5095 // 共享内存的key。
    #define SEMKEYP 0x5095 // 信号量的key。

    class cpactive // 实现进程心跳的类
    {
    private:
        int shmid;
        int m_pos;
        st_procinfo *m_shm;

    public:
        cpactive();
        bool init(int timeout,string pname="",clogfile *logfile=nullptr);  //这里传指针视为了选择是否使用日志打印
        bool update();  //更新心跳时间
        int getshmid();
        int getpos();
        ~cpactive();
    };
}
#endif _PUBLIC_H